import React, { ChangeEvent, Suspense, useState, useTransition } from 'react';
import './App.css';
import User from './interface'

interface AppProps {
  headerText: string,
  extraText?: string | number,
  list?: number[]
}

interface IFromData {
  name: string,
  age: number | string
}

// recommend use the union type, do not use enum
type ECheckState = 'details' | 'shipping' | 'payment'
// enum ECheckState {
//   details = 'details',
//   shipping = 'shipping',
//   payment = 'payment',
// }

const userList = [
  { id: 1, name: 'sdf', age: 22 },
  { id: 2, name: 'sdf', age: 22 },
  { id: 3, name: 'sdf', age: 22 },
]
const orderList = [
  { id: 1, amout: 34, price: 22 },
  { id: 2, amout: 34, price: 22 },
  { id: 3, amout: 34, price: 22 },
]
interface Item {
  id: number
}
interface GridProps<T> {
  items: T[]
}

// use generic constraint
// prop items must be a T type array, and T must have id field
const Grid = <T extends Item,>({ items }: GridProps<T>) => {
  return (
    <ul>
      {items.map(item => {
        return <li key={item.id}>{JSON.stringify(item)}</li>
      })}
    </ul>
  )
}

// some types
const arr: number[] = [1, 2]
const tuple: [string, boolean, ...number[]] = ['11', true, 1, 1]

// generic
const swap = <T, U>(tuple: [T, U]): [U, T] => {
  return [tuple[1], tuple[0]]
}
swap([1, '1'])

// generic constraint(/kən'streɪnt/) by using extends
interface IWithLenght {
  length: number
}
// arguments arg must have the field of length, such as array/string/some object with field length
const echoLength = <T extends IWithLenght,>(arg: T): number => {
  return arg.length;
}

// class generic
class Queue<T>{
  private data: T[] = [];
  push(item: T) {
    return this.data.push(item)
  }
  pop() {
    return this.data.pop()
  }
}
const q1 = new Queue<number>()
q1.push(123)
console.log(11, q1.pop())
const q2 = new Queue<string>()
q2.push('sfd')
console.log(22, q2.pop())

// interface generic
interface keyPair<T, U> {
  key: T;
  value: U
}
const p1: keyPair<string, number> = { key: '11', value: 11 }
const p2: keyPair<number, string> = { key: 11, value: '11' }

// type alias
type NameResolver = () => string
const getName = (n: string | NameResolver): string => {
  if (typeof n === 'string') {
    return n
  } else {
    return n()
  }
}

// type assertion
const getLength = (arg: number | string): number => {
  // when arg assert to be a string, arg will have the methods of string
  const str = arg as string
  if(str.length) {
    return str.length
  } else {
    const num = arg as number
    return num.toString().length
  }
}

// when we install some library such as axios
// it will install index.d.ts to the path of node_modules/@types/axios/index.d.ts
// because we have the index.d.ts of axios
// when you edit axios in the editor, editor can hint code about axios
// most of library have d.ts file when you install them
// if some library don't have d.ts, you can write d.ts file for it
// for example, after I created ./test.d.ts(any name is ok but must xx.d.ts, at last you should change this file to the path node_modules/@types/test/index.d.ts)
// now my eidtor can hint code about the declares fn1/mylib/calculator
// but they can not really work, because I never import fn1/mylib/calculator from any library
// fn1('12')
// mylib.get('aa')
// mylib.post('aa', 11)
// calculator('plus', [1, 2])
// calculator.plus([1, 2])
// calculator.minus([2, 3])

// global objects
const arr1: Array<number> = [1, 2]
const date = new Date()
date.getTime()
// build-in objects
Math.abs(-1)
// DOM BOM
const allLi = document.querySelectorAll('body')
allLi[0].onclick = () => {}
// utility types
interface IPerson {
  name: string;
  age: number
}
// when use utility type Partial, IPerson's fields name and age is optional
type IPartial = Partial<IPerson>
const luo: IPartial = {}



function App({ headerText, extraText = 'default text', list }: AppProps) {
  const [user, setUser] = useState<User | null>(null)

  const [formData, setFormData] = useState<IFromData>({ name: '', age: '' })
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFormData((prevState) => {
      return {
        ...prevState,
        [e.target.id]: e.target.value
      }
    })
  }
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    alert(JSON.stringify(formData))
    setFormData(() => {
      return { name: '', age: '' }
    })
  }

  const [checkState, setCheckState] = useState<ECheckState>('details')

  // Transition API
  const [value, setValue] = useState<string>('')
  const [arr, setArr] = useState<number[]>([])
  const [isPending, startTransition] = useTransition()
  const onChangeHandle = (e: ChangeEvent<HTMLInputElement>) => {
    const v = e.target.value
    setValue(v)
    startTransition(() => {
      // when use the useTransition hook
      // setValue is more important than setArr which is in the callback of startTransition
      // so when it is running the setValue method, the method of setArr will interrupt
      // isPending will show if the method of setArr is runing
      const a = Array.from({ length: 10000 }, (_, i) => new Date().getTime() + i)
      setArr(a)
    })
  }

  
  // Suspense 1
  // const Page = React.lazy(() => import('./xxxx'))
  // // show a spinner while the page is loading
  // <Suspense fallback={<Spinner />}>
  //   <Page />
  // </Suspense>

  return (
    <div className="App">
      <div>
        {/* 1 define the props types */}
        <h1>{headerText}</h1>
        <p>{extraText}</p>
        <p>{list?.join('-')}</p>
      </div>

      <div>
        {/* 2 how to use useState with TS */}
        <button onClick={(): void => setUser({ name: 'luo' })}>click me to get the user name</button>
        <p>{user?.name}</p>
      </div>

      <div>
        {/* 3 move you cursor over the letter e, you can see the type of e */}
        <form onSubmit={e => onSubmit(e)}>
          <label htmlFor="name">name</label>
          <input type="text" id='name' value={formData?.name} onChange={e => onChange(e)} /><br />
          <label htmlFor="age">age</label>
          <input type="number" id='age' value={formData?.age} onChange={onChange} /><br />
          <button type='submit'>upload post</button>
        </form>
      </div>

      <div style={{marginTop: '20px'}}>
        { 
          checkState === 'details' && 
          <div>
            <span>details</span>
            {/* defined ECheckState as a union type,  
            if the arguments of method setCheckState are not 'details' | 'shipping' | 'payment'
            it will show a error in eidtor */}
            <button type='button' onClick={() => setCheckState('shipping')}>next</button>
          </div>
        }
        { 
          checkState === 'shipping' && 
          <div>
            <span>shipping</span>
            <button type='button' onClick={() => setCheckState('payment')}>next</button>
          </div>
        }
        { 
          checkState === 'payment' && 
          <div>
            <span>payment</span>
            <button type='button' onClick={() => setCheckState('details')}>next</button>
          </div>
        }
      </div>
      
      <div>
        {/* generic */}
        <Grid items={userList}></Grid>
      </div>

      <div>
        <input type="text" value={value} onChange={(e) => onChangeHandle(e)} />
        { isPending && <h1>⏳</h1> }
        { arr?.map(item => <p key={item}>{item}</p>) }
      </div>

    </div>
  );
}

export default App;
